// SPDX-License-Identifier: Mulan PSL v2
/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This software is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use crate::error::{ServerError, ServerResult};

/// Default shared memory path for client-to-server communication.
pub const DEFAULT_C2S_SHMEM_PATH: &str = "/xgpu_c2s";

/// Default shared memory path for server-to-client communication.
pub const DEFAULT_S2C_SHMEM_PATH: &str = "/xgpu_s2c";

/// Default shared memory buffer size (16MB).
pub const DEFAULT_SHMEM_SIZE: usize = 16 * 1024 * 1024;

/// Default maximum frame size (8MB).
pub const DEFAULT_MAX_FRAME_SIZE: usize = 8 * 1024 * 1024;

/// Default request timeout in milliseconds.
pub const DEFAULT_REQUEST_TIMEOUT_MS: u64 = 5000;

/// Configuration for the xgpu-server.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    /// Shared memory configuration.
    pub shmem: ShmemConfig,
    
    /// Framing configuration.
    pub framing: FramingConfig,
    
    /// GPU resource management configuration.
    pub gpu: GpuConfig,
    
    /// Logging configuration.
    pub logging: LoggingConfig,
    
    /// Performance tuning configuration.
    pub performance: PerformanceConfig,
}

/// Shared memory configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShmemConfig {
    /// Path for client-to-server shared memory.
    pub c2s_path: String,
    
    /// Path for server-to-client shared memory.
    pub s2c_path: String,
    
    /// Size of each shared memory segment in bytes.
    pub buffer_size: usize,
}

/// Framing configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FramingConfig {
    /// Maximum frame size in bytes.
    pub max_frame_size: usize,
}

/// GPU resource management configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpuConfig {
    /// Enable GPU resource pooling.
    pub enable_pooling: bool,
    
    /// Maximum number of concurrent allocations per device.
    pub max_allocations_per_device: usize,
    
    /// Memory pool initial size per device (in bytes).
    pub initial_pool_size: usize,
    
    /// List of GPU device IDs to use (empty means use all available).
    pub device_ids: Vec<u32>,
}

/// Logging configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    /// Log level (error, warn, info, debug, trace).
    pub level: String,
    
    /// Enable request/response logging.
    pub log_requests: bool,
    
    /// Enable performance metrics logging.
    pub log_metrics: bool,
}

/// Performance tuning configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
    /// Request timeout in milliseconds.
    pub request_timeout_ms: u64,
    
    /// Number of worker threads for request processing.
    pub worker_threads: Option<usize>,
    
    /// Enable async I/O for shared memory operations.
    pub async_io: bool,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            shmem: ShmemConfig::default(),
            framing: FramingConfig::default(),
            gpu: GpuConfig::default(),
            logging: LoggingConfig::default(),
            performance: PerformanceConfig::default(),
        }
    }
}

impl Default for ShmemConfig {
    fn default() -> Self {
        Self {
            c2s_path: DEFAULT_C2S_SHMEM_PATH.to_string(),
            s2c_path: DEFAULT_S2C_SHMEM_PATH.to_string(),
            buffer_size: DEFAULT_SHMEM_SIZE,
        }
    }
}

impl Default for FramingConfig {
    fn default() -> Self {
        Self {
            max_frame_size: DEFAULT_MAX_FRAME_SIZE,
        }
    }
}

impl Default for GpuConfig {
    fn default() -> Self {
        Self {
            enable_pooling: true,
            max_allocations_per_device: 1000,
            initial_pool_size: 256 * 1024 * 1024, // 256MB
            device_ids: vec![], // Empty means use all available devices
        }
    }
}

impl Default for LoggingConfig {
    fn default() -> Self {
        Self {
            level: "info".to_string(),
            log_requests: false,
            log_metrics: true,
        }
    }
}

impl Default for PerformanceConfig {
    fn default() -> Self {
        Self {
            request_timeout_ms: DEFAULT_REQUEST_TIMEOUT_MS,
            worker_threads: None, // Use tokio default
            async_io: true,
        }
    }
}

impl ServerConfig {
    /// Load configuration from a JSON file.
    pub fn from_file<P: AsRef<Path>>(path: P) -> ServerResult<Self> {
        let content = fs::read_to_string(path)
            .map_err(|e| ServerError::configuration(format!("Failed to read config file: {}", e)))?;
        
        let config: ServerConfig = serde_json::from_str(&content)?;
        config.validate()?;
        Ok(config)
    }
    
    /// Save configuration to a JSON file.
    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> ServerResult<()> {
        let content = serde_json::to_string_pretty(self)?;
        fs::write(path, content)
            .map_err(|e| ServerError::configuration(format!("Failed to write config file: {}", e)))?;
        Ok(())
    }
    
    /// Load configuration from environment variables and command line arguments.
    /// Falls back to default configuration if not specified.
    pub fn from_env() -> ServerResult<Self> {
        let mut config = Self::default();
        
        // Override with environment variables if present
        if let Ok(c2s_path) = std::env::var("XGPU_C2S_SHMEM_PATH") {
            config.shmem.c2s_path = c2s_path;
        }
        if let Ok(s2c_path) = std::env::var("XGPU_S2C_SHMEM_PATH") {
            config.shmem.s2c_path = s2c_path;
        }
        if let Ok(buffer_size) = std::env::var("XGPU_SHMEM_SIZE") {
            config.shmem.buffer_size = buffer_size.parse()
                .map_err(|e| ServerError::configuration(format!("Invalid XGPU_SHMEM_SIZE: {}", e)))?;
        }
        if let Ok(log_level) = std::env::var("XGPU_LOG_LEVEL") {
            config.logging.level = log_level;
        }
        
        config.validate()?;
        Ok(config)
    }
    
    /// Validate the configuration.
    pub fn validate(&self) -> ServerResult<()> {
        // Validate shared memory configuration
        if self.shmem.buffer_size < 1024 * 1024 {
            return Err(ServerError::configuration(
                "Shared memory buffer size must be at least 1MB"
            ));
        }
        
        if self.shmem.c2s_path == self.shmem.s2c_path {
            return Err(ServerError::configuration(
                "Client-to-server and server-to-client paths must be different"
            ));
        }
        
        // Validate framing configuration
        if self.framing.max_frame_size > self.shmem.buffer_size / 2 {
            return Err(ServerError::configuration(
                "Maximum frame size must be less than half the buffer size"
            ));
        }
        
        // Validate GPU configuration
        if self.gpu.max_allocations_per_device == 0 {
            return Err(ServerError::configuration(
                "Maximum allocations per device must be greater than 0"
            ));
        }
        
        // Validate logging configuration
        match self.logging.level.as_str() {
            "error" | "warn" | "info" | "debug" | "trace" => {},
            _ => return Err(ServerError::configuration(
                "Invalid log level. Must be one of: error, warn, info, debug, trace"
            )),
        }
        
        // Validate performance configuration
        if self.performance.request_timeout_ms == 0 {
            return Err(ServerError::configuration(
                "Request timeout must be greater than 0"
            ));
        }
        
        Ok(())
    }
}